Index: .classpath =================================================================== --- .classpath (revision 1688079) +++ .classpath (working copy) @@ -179,6 +179,7 @@ + Index: specialpurpose/build.xml =================================================================== --- specialpurpose/build.xml (revision 1688079) +++ specialpurpose/build.xml (working copy) @@ -37,6 +37,7 @@ projectmgr/build.xml, scrum/build.xml, bi/build.xml, - webpos/build.xml + webpos/build.xml, + passport/build.xml "/> Index: specialpurpose/component-load.xml =================================================================== --- specialpurpose/component-load.xml (revision 1688079) +++ specialpurpose/component-load.xml (working copy) @@ -40,4 +40,5 @@ + Index: specialpurpose/ecommerce/webapp/ecommerce/customer/newcustomer.ftl =================================================================== --- specialpurpose/ecommerce/webapp/ecommerce/customer/newcustomer.ftl (revision 1688079) +++ specialpurpose/ecommerce/webapp/ecommerce/customer/newcustomer.ftl (working copy) @@ -51,8 +51,8 @@ } } function hideShowUsaStates() { - var customerStateElement = document.getElementById('newuserform_stateProvinceGeoId'); - var customerCountryElement = document.getElementById('newuserform_countryGeoId'); + var customerStateElement = document.getElementById('newuserform_stateProvinceGeoId'); + var customerCountryElement = document.getElementById('newuserform_countryGeoId'); if (customerCountryElement.value == "USA" || customerCountryElement.value == "UMI") { customerStateElement.style.display = "block"; } else { Index: specialpurpose/ecommerce/webapp/ecomseo/WEB-INF/controller.xml =================================================================== --- specialpurpose/ecommerce/webapp/ecomseo/WEB-INF/controller.xml (revision 1688079) +++ specialpurpose/ecommerce/webapp/ecomseo/WEB-INF/controller.xml (working copy) @@ -21,6 +21,7 @@ + OFBiz: eCommerce SEO Controller Configuration File Index: specialpurpose/ecommerce/widget/CommonScreens.xml =================================================================== --- specialpurpose/ecommerce/widget/CommonScreens.xml (revision 1688079) +++ specialpurpose/ecommerce/widget/CommonScreens.xml (working copy) @@ -336,6 +336,7 @@ + Index: specialpurpose/passport/README =================================================================== --- specialpurpose/passport/README (revision 0) +++ specialpurpose/passport/README (revision 0) @@ -0,0 +1,4 @@ +OFBiz Passport Component + +To support users to login with a third party OAuth2 authentication such as Github, LinkedIn and etc. + Index: specialpurpose/passport/build.xml =================================================================== --- specialpurpose/passport/build.xml (revision 0) +++ specialpurpose/passport/build.xml (revision 0) @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file Index: specialpurpose/passport/config/PassportUiLabels.xml =================================================================== --- specialpurpose/passport/config/PassportUiLabels.xml (revision 0) +++ specialpurpose/passport/config/PassportUiLabels.xml (revision 0) @@ -0,0 +1,98 @@ + + + + + + Passport Third Party Auth Application + 第三方认证应用程序 + + + Third Party Login + 第三方登录 + + + + + Failed to match the state parameter in LinkedIn response. Just try to login again. + 领英响应中的state参数不一致。重试登录即可。 + + + Error while redirecting to LinkedIn authorization URL. Please use other login method for now. + 转到领英认证网址时出错。请先使用其它登录方式。 + + + Error while redirecting to LinkedIn authorization URL: ${errorMessage} + 转到领英认证网址时出错:${errorMessage}。 + + + Failed to get LinkedIn authorization code.<br>Error: ${error}<br>Error description: ${error_description} + 获取领英授权码时出错。<br>错误: ${error}<br>错误描述: ${error_description} + + + Failed to get LinkedIn authorization code. + 获取领英授权码时出错。 + + + Failed to get LinkedIn configuration. Please check whether OAuth2LinkedIn entity has your productStoreId configured. + 获取领英配置时出错。请检查OAuth2LinkedIn实体中是否配置了你的店铺标识(productStoreId)。 + + + Failed to get LinkedIn access token. + 获取领英访问令牌时出错。 + + + Failed to get OAuth2LinkedIn entity: ${errorMessage} + 获取OAuth2LinkedIn实体时出错:${errorMessage} + + + + + Error while redirecting to GitHub authorization URL. Please use other login method for now. + 转到GitHub认证网址时出错。请先使用其它登录方式。 + + + Error while redirecting to GitHub authorization URL: ${errorMessage} + 转到GitHub认证网址时出错:${errorMessage}。 + + + Failed to match the state parameter in GitHub response. Just try to login again. + GitHub响应中的state参数不一致。重试登录即可。 + + + Failed to get LinkedIn authorization code.<br>Error: ${error}<br>Error description: ${error_description} + 获取领英授权码时出错。<br>错误: ${error}<br>错误描述: ${error_description} + + + Failed to get GitHub authorization code. + 获取GitHub授权码时出错。 + + + Failed to get GitHub configuration. Please check whether OAuth2GitHub entity has your productStoreId configured. + 获取GitHub配置时出错。请检查OAuth2GitHub实体中是否配置了你的店铺标识(productStoreId)。 + + + Failed to get GitHub access token: ${error} + 获取GitHub访问令牌时出错:${error} + + + Failed to get OAuth2GitHub entity: ${errorMessage} + 获取OAuth2GitHub实体时出错:${errorMessage} + + Index: specialpurpose/passport/config/gitHubAuth.properties =================================================================== --- specialpurpose/passport/config/gitHubAuth.properties (revision 0) +++ specialpurpose/passport/config/gitHubAuth.properties (revision 0) @@ -0,0 +1,24 @@ +############################################################################### +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +############################################################################### + +github.env.prefix=test + +github.authenticator.enabled=true + +# github.group.map.1=github-user=FULLADMIN Index: specialpurpose/passport/config/linkedInAuth.properties =================================================================== --- specialpurpose/passport/config/linkedInAuth.properties (revision 0) +++ specialpurpose/passport/config/linkedInAuth.properties (revision 0) @@ -0,0 +1,24 @@ +############################################################################### +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +############################################################################### + +linkedin.env.prefix=test + +linkedin.authenticator.enabled=true + +# linkedin.group.map.1=linkedin-user=FULLADMIN Index: specialpurpose/passport/data/OAuth2CommonSeedData.xml =================================================================== --- specialpurpose/passport/data/OAuth2CommonSeedData.xml (revision 0) +++ specialpurpose/passport/data/OAuth2CommonSeedData.xml (revision 0) @@ -0,0 +1,28 @@ + + + + + + + + + + + \ No newline at end of file Index: specialpurpose/passport/data/OAuth2GitHubSeedData.xml =================================================================== --- specialpurpose/passport/data/OAuth2GitHubSeedData.xml (revision 0) +++ specialpurpose/passport/data/OAuth2GitHubSeedData.xml (revision 0) @@ -0,0 +1,34 @@ + + + + + + + + + + + + + \ No newline at end of file Index: specialpurpose/passport/data/OAuth2LinkedInSeedData.xml =================================================================== --- specialpurpose/passport/data/OAuth2LinkedInSeedData.xml (revision 0) +++ specialpurpose/passport/data/OAuth2LinkedInSeedData.xml (revision 0) @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + \ No newline at end of file Index: specialpurpose/passport/entitydef/entitymodel.xml =================================================================== --- specialpurpose/passport/entitydef/entitymodel.xml (revision 0) +++ specialpurpose/passport/entitydef/entitymodel.xml (revision 0) @@ -0,0 +1,121 @@ + + + + + Entity of Passport Component + Data Model Enhancements For Passport + 1.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file Index: specialpurpose/passport/ofbiz-component.xml =================================================================== --- specialpurpose/passport/ofbiz-component.xml (revision 0) +++ specialpurpose/passport/ofbiz-component.xml (revision 0) @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file Index: specialpurpose/passport/src/org/ofbiz/passport/event/GitHubEvents.java =================================================================== --- specialpurpose/passport/src/org/ofbiz/passport/event/GitHubEvents.java (revision 0) +++ specialpurpose/passport/src/org/ofbiz/passport/event/GitHubEvents.java (revision 0) @@ -0,0 +1,322 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + *******************************************************************************/ +package org.ofbiz.passport.event; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.Map; +import java.util.Random; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpException; +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.cookie.CookiePolicy; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.params.HttpMethodParams; +import org.ofbiz.passport.user.GitHubAuthenticator; +import org.ofbiz.passport.util.PassportUtil; +import org.ofbiz.base.conversion.ConversionException; +import org.ofbiz.base.conversion.JSONConverters.JSONToMap; +import org.ofbiz.base.crypto.HashCrypt; +import org.ofbiz.base.lang.JSON; +import org.ofbiz.base.util.Debug; +import org.ofbiz.base.util.UtilHttp; +import org.ofbiz.base.util.UtilMisc; +import org.ofbiz.base.util.UtilProperties; +import org.ofbiz.base.util.UtilValidate; +import org.ofbiz.common.authentication.api.AuthenticatorException; +import org.ofbiz.common.login.LoginServices; +import org.ofbiz.entity.Delegator; +import org.ofbiz.entity.GenericEntityException; +import org.ofbiz.entity.GenericValue; +import org.ofbiz.entity.util.EntityUtil; +import org.ofbiz.product.store.ProductStoreWorker; +import org.ofbiz.service.LocalDispatcher; + +/** + * GitHubEvents - Events for GitHub login. + * + * Refs: https://developer.github.com/v3/oauth/ + * + */ +public class GitHubEvents { + + public static final String module = GitHubEvents.class.getName(); + + public static final String resource = "PassportUiLabels"; + + public static final String AuthorizeUri = "/login/oauth/authorize"; + + public static final String TokenServiceUri = "/login/oauth/access_token"; + + public static final String UserApiUri = "/user"; + + public static final String DEFAULT_SCOPE = "user,gist"; + + public static final String ApiEndpoint = "https://api.github.com"; + + public static final String TokenEndpoint = "https://github.com"; + + public static final String SESSION_GITHUB_STATE = "_GITHUB_STATE_"; + + public static final String envPrefix = UtilProperties.getPropertyValue(GitHubAuthenticator.props, "github.env.prefix", "test"); + + /** + * Redirect to GitHub login page. + * + * @return + */ + public static String gitHubRedirect(HttpServletRequest request, HttpServletResponse response) { + GenericValue oauth2GitHub = getOAuth2GitHubConfig(request); + if (UtilValidate.isEmpty(oauth2GitHub)) { + return "error"; + } + + String clientId = oauth2GitHub.getString(PassportUtil.COMMON_CLIENT_ID); + String returnURI = oauth2GitHub.getString(PassportUtil.COMMON_RETURN_RUL); + + // Get user authorization code + try { + String state = System.currentTimeMillis() + String.valueOf((new Random(10)).nextLong()); + request.getSession().setAttribute(SESSION_GITHUB_STATE, state); + String redirectUrl = TokenEndpoint + AuthorizeUri + + "?client_id=" + clientId + + "&scope=" + DEFAULT_SCOPE + + "&redirect_uri=" + URLEncoder.encode(returnURI, "UTF-8") + + "&state=" + state; + Debug.logInfo("Request to GitHub: " + redirectUrl, module); + response.sendRedirect(redirectUrl); + } catch (NullPointerException e) { + String errMsg = UtilProperties.getMessage(resource, "RedirectToGitHubOAuth2NullException", UtilHttp.getLocale(request)); + request.setAttribute("_ERROR_MESSAGE_", errMsg); + return "error"; + } catch (IOException e) { + Map messageMap = UtilMisc.toMap("errorMessage", e.toString()); + String errMsg = UtilProperties.getMessage(resource, "RedirectToGitHubOAuth2Error", messageMap, UtilHttp.getLocale(request)); + request.setAttribute("_ERROR_MESSAGE_", errMsg); + return "error"; + } + + return "success"; + } + + /** + * Parse GitHub login response and login the user if possible. + * + * @return + */ + public static String parseGitHubResponse(HttpServletRequest request, HttpServletResponse response) { + String authorizationCode = request.getParameter(PassportUtil.COMMON_CODE); + String state = request.getParameter(PassportUtil.COMMON_STATE); + if (!state.equals(request.getSession().getAttribute(SESSION_GITHUB_STATE))) { + String errMsg = UtilProperties.getMessage(resource, "GitHubFailedToMatchState", UtilHttp.getLocale(request)); + request.setAttribute("_ERROR_MESSAGE_", errMsg); + return "error"; + } + if (UtilValidate.isEmpty(authorizationCode)) { + String error = request.getParameter(PassportUtil.COMMON_ERROR); + String errorDescpriton = request.getParameter(PassportUtil.COMMON_ERROR_DESCRIPTION); + String errMsg = null; + try { + errMsg = UtilProperties.getMessage(resource, "FailedToGetGitHubAuthorizationCode", UtilMisc.toMap(PassportUtil.COMMON_ERROR, error, PassportUtil.COMMON_ERROR_DESCRIPTION, URLDecoder.decode(errorDescpriton, "UTF-8")), UtilHttp.getLocale(request)); + } catch (UnsupportedEncodingException e) { + errMsg = UtilProperties.getMessage(resource, "GetGitHubAuthorizationCodeError", UtilHttp.getLocale(request)); + } + request.setAttribute("_ERROR_MESSAGE_", errMsg); + return "error"; + } + Debug.logInfo("GitHub authorization code: " + authorizationCode, module); + + GenericValue oauth2GitHub = getOAuth2GitHubConfig(request); + if (UtilValidate.isEmpty(oauth2GitHub)) { + String errMsg = UtilProperties.getMessage(resource, "GetOAuth2GitHubConfigError", UtilHttp.getLocale(request)); + request.setAttribute("_ERROR_MESSAGE_", errMsg); + return "error"; + } + String clientId = oauth2GitHub.getString(PassportUtil.COMMON_CLIENT_ID); + String secret = oauth2GitHub.getString(PassportUtil.COMMON_CLIENT_SECRET); + String returnURI = oauth2GitHub.getString(PassportUtil.COMMON_RETURN_RUL); + + // Grant token from authorization code and oauth2 token + // Use the authorization code to obtain an access token + String accessToken = null; + String tokenType = null; + + HttpClient jsonClient = new HttpClient(); + PostMethod postMethod = new PostMethod(TokenEndpoint + TokenServiceUri); + try { + HttpMethodParams params = new HttpMethodParams(); + String queryString = "client_id=" + clientId + + "&client_secret=" + secret + + "&code=" + authorizationCode + + "&redirect_uri=" + URLEncoder.encode(returnURI, "UTF-8"); + // Debug.logInfo("GitHub get access token query string: " + queryString, module); + postMethod.setQueryString(queryString); + params.setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY); + postMethod.setParams(params); + postMethod.setRequestHeader(PassportUtil.ACCEPT_HEADER, "application/json"); + jsonClient.executeMethod(postMethod); + // Debug.logInfo("GitHub get access token response code: " + postMethod.getStatusCode(), module); + // Debug.logInfo("GitHub get access token response content: " + postMethod.getResponseBodyAsString(1024), module); + if (postMethod.getStatusCode() == HttpStatus.SC_OK) { + // Debug.logInfo("Json Response from GitHub: " + postMethod.getResponseBodyAsString(1024), module); + JSON jsonObject = JSON.from(postMethod.getResponseBodyAsString(1024)); + JSONToMap jsonMap = new JSONToMap(); + Map userMap = jsonMap.convert(jsonObject); + accessToken = (String) userMap.get("access_token"); + tokenType = (String) userMap.get("token_type"); + // Debug.logInfo("Generated Access Token : " + accessToken, module); + // Debug.logInfo("Token Type: " + tokenType, module); + } else { + String errMsg = UtilProperties.getMessage(resource, "GetOAuth2GitHubAccessTokenError", UtilMisc.toMap("error", postMethod.getResponseBodyAsString()), UtilHttp.getLocale(request)); + request.setAttribute("_ERROR_MESSAGE_", errMsg); + return "error"; + } + } catch (UnsupportedEncodingException e) { + request.setAttribute("_ERROR_MESSAGE_", e.toString()); + return "error"; + } catch (HttpException e) { + request.setAttribute("_ERROR_MESSAGE_", e.toString()); + return "error"; + } catch (IOException e) { + request.setAttribute("_ERROR_MESSAGE_", e.toString()); + return "error"; + } catch (ConversionException e) { + request.setAttribute("_ERROR_MESSAGE_", e.toString()); + return "error"; + } finally { + postMethod.releaseConnection(); + } + + // Get User Profile + GetMethod getMethod = new GetMethod(ApiEndpoint + UserApiUri); + Map userInfo = null; + try { + userInfo = GitHubAuthenticator.getUserInfo(getMethod, accessToken, tokenType, UtilHttp.getLocale(request)); + } catch (HttpException e) { + request.setAttribute("_ERROR_MESSAGE_", e.toString()); + return "error"; + } catch (IOException e) { + request.setAttribute("_ERROR_MESSAGE_", e.toString()); + return "error"; + } catch (AuthenticatorException e) { + request.setAttribute("_ERROR_MESSAGE_", e.toString()); + return "error"; + } finally { + getMethod.releaseConnection(); + } + // Debug.logInfo("GitHub User Info:" + userInfo, module); + + // Store the user info and check login the user + return checkLoginGitHubUser(request, userInfo, accessToken); + } + + private static String checkLoginGitHubUser(HttpServletRequest request, Map userInfo, String accessToken) { + Delegator delegator = (Delegator) request.getAttribute("delegator"); + LocalDispatcher dispatcher = (LocalDispatcher) request.getAttribute("dispatcher"); + String productStoreId = ProductStoreWorker.getProductStoreId(request); + String gitHubUserId = (String) userInfo.get("login"); + GenericValue gitHubUser = null; + try { + gitHubUser = delegator.findOne("GitHubUser", UtilMisc.toMap("gitHubUserId", gitHubUserId), false); + } catch (GenericEntityException e) { + request.setAttribute("_ERROR_MESSAGE_", e.getMessage()); + return "error"; + } + if (UtilValidate.isNotEmpty(gitHubUser)) { + boolean dataChanged = false; + if (!accessToken.equals(gitHubUser.getString("accessToken"))) { + gitHubUser.set("accessToken", accessToken); + dataChanged = true; + } + if (!envPrefix.equals(gitHubUser.getString("envPrefix"))) { + gitHubUser.set("envPrefix", envPrefix); + dataChanged = true; + } + if (!productStoreId.equals(gitHubUser.getString("productStoreId"))) { + gitHubUser.set("productStoreId", productStoreId); + dataChanged = true; + } + if (dataChanged) { + try { + gitHubUser.store(); + } catch (GenericEntityException e) { + Debug.logError(e.getMessage(), module); + } + } + } else { + gitHubUser = delegator.makeValue("GitHubUser", UtilMisc.toMap("accessToken", accessToken, + "productStoreId", productStoreId, + "envPrefix", envPrefix, + "gitHubUserId", gitHubUserId)); + try { + gitHubUser.create(); + } catch (GenericEntityException e) { + Debug.logError(e.getMessage(), module); + } + } + try { + GenericValue userLogin = EntityUtil.getFirst(delegator.findByAnd("UserLogin", UtilMisc.toMap("externalAuthId", gitHubUserId), null, false)); + GitHubAuthenticator authn = new GitHubAuthenticator(); + authn.initialize(dispatcher); + if (UtilValidate.isEmpty(userLogin)) { + String userLoginId = authn.createUser(userInfo); + userLogin = delegator.findOne("UserLogin", UtilMisc.toMap("userLoginId", userLoginId), false); + } + String password = PassportUtil.randomString(); + boolean useEncryption = "true".equals(UtilProperties.getPropertyValue("security.properties", "password.encrypt")); + userLogin.set("currentPassword", useEncryption ? HashCrypt.digestHash(LoginServices.getHashType(), null, password) : password); + userLogin.store(); + request.setAttribute("USERNAME", userLogin.getString("userLoginId")); + request.setAttribute("PASSWORD", password); + } catch (GenericEntityException e) { + Debug.logError(e.getMessage(), module); + request.setAttribute("_ERROR_MESSAGE_", e.toString()); + return "error"; + } catch (AuthenticatorException e) { + Debug.logError(e.getMessage(), module); + request.setAttribute("_ERROR_MESSAGE_", e.toString()); + return "error"; + } + return "success"; + } + + public static GenericValue getOAuth2GitHubConfig(HttpServletRequest request) { + Delegator delegator = (Delegator) request.getAttribute("delegator"); + String productStoreId = ProductStoreWorker.getProductStoreId(request); + try { + return getOAuth2GitHubConfig(delegator, productStoreId); + } catch (GenericEntityException e) { + Map messageMap = UtilMisc.toMap("errorMessage", e.toString()); + String errMsg = UtilProperties.getMessage(resource, "GetOAuth2GitHubError", messageMap, UtilHttp.getLocale(request)); + request.setAttribute("_ERROR_MESSAGE_", errMsg); + } + return null; + } + + public static GenericValue getOAuth2GitHubConfig(Delegator delegator, String productStoreId) throws GenericEntityException { + return EntityUtil.getFirst(EntityUtil.filterByDate(delegator.findByAnd("OAuth2GitHub", UtilMisc.toMap("productStoreId", productStoreId), null, false))); + } +} Index: specialpurpose/passport/src/org/ofbiz/passport/event/LinkedInEvents.java =================================================================== --- specialpurpose/passport/src/org/ofbiz/passport/event/LinkedInEvents.java (revision 0) +++ specialpurpose/passport/src/org/ofbiz/passport/event/LinkedInEvents.java (revision 0) @@ -0,0 +1,326 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + *******************************************************************************/ +package org.ofbiz.passport.event; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.Map; +import java.util.Random; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpException; +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.cookie.CookiePolicy; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.params.HttpMethodParams; +import org.ofbiz.passport.user.LinkedInAuthenticator; +import org.ofbiz.passport.util.PassportUtil; +import org.ofbiz.base.conversion.ConversionException; +import org.ofbiz.base.conversion.JSONConverters.JSONToMap; +import org.ofbiz.base.crypto.HashCrypt; +import org.ofbiz.base.lang.JSON; +import org.ofbiz.base.util.Debug; +import org.ofbiz.base.util.UtilHttp; +import org.ofbiz.base.util.UtilMisc; +import org.ofbiz.base.util.UtilProperties; +import org.ofbiz.base.util.UtilValidate; +import org.ofbiz.common.authentication.api.AuthenticatorException; +import org.ofbiz.common.login.LoginServices; +import org.ofbiz.entity.Delegator; +import org.ofbiz.entity.GenericEntityException; +import org.ofbiz.entity.GenericValue; +import org.ofbiz.entity.util.EntityUtil; +import org.ofbiz.product.store.ProductStoreWorker; +import org.ofbiz.service.LocalDispatcher; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +/** + * LinkedEvents - Events for LinkedIn login. + * + * Refs: https://developer.linkedin.com/documents/authentication + * + */ +public class LinkedInEvents { + + public static final String module = LinkedInEvents.class.getName(); + + public static final String resource = "PassportUiLabels"; + + public static final String AuthorizeUri = "/uas/oauth2/authorization"; + + public static final String TokenServiceUri = "/uas/oauth2/accessToken"; + + public static final String UserApiUri = "/v1/people/~"; + + public static final String DEFAULT_SCOPE = "r_basicprofile%20r_emailaddress"; + + public static final String TokenEndpoint = "https://www.linkedin.com"; + + public static final String SESSION_LINKEDIN_STATE = "_LINKEDIN_STATE_"; + + public static final String envPrefix = UtilProperties.getPropertyValue(LinkedInAuthenticator.props, "linkedin.env.prefix", "test"); + + /** + * Redirect to LinkedIn login page. + * + * @return + */ + public static String linkedInRedirect(HttpServletRequest request, HttpServletResponse response) { + GenericValue oauth2LinkedIn = getOAuth2LinkedInConfig(request); + if (UtilValidate.isEmpty(oauth2LinkedIn)) { + return "error"; + } + + String clientId = oauth2LinkedIn.getString(PassportUtil.ApiKeyLabel); + String returnURI = oauth2LinkedIn.getString(envPrefix + PassportUtil.ReturnUrlLabel); + + // Get user authorization code + try { + String state = System.currentTimeMillis() + String.valueOf((new Random(10)).nextLong()); + request.getSession().setAttribute(SESSION_LINKEDIN_STATE, state); + String redirectUrl = TokenEndpoint + AuthorizeUri + + "?client_id=" + clientId + + "&response_type=code" + + "&scope=" + DEFAULT_SCOPE + + "&redirect_uri=" + URLEncoder.encode(returnURI, "UTF-8") + + "&state=" + state; + response.sendRedirect(redirectUrl); + } catch (NullPointerException e) { + String errMsg = UtilProperties.getMessage(resource, "RedirectToLinkedInOAuth2NullException", UtilHttp.getLocale(request)); + request.setAttribute("_ERROR_MESSAGE_", errMsg); + return "error"; + } catch (IOException e) { + Map messageMap = UtilMisc.toMap("errorMessage", e.toString()); + String errMsg = UtilProperties.getMessage(resource, "RedirectToLinkedInOAuth2Error", messageMap, UtilHttp.getLocale(request)); + request.setAttribute("_ERROR_MESSAGE_", errMsg); + return "error"; + } + + return "success"; + } + + /** + * Parse LinkedIn login response and login the user if possible. + * + * @return + */ + public static String parseLinkedInResponse(HttpServletRequest request, HttpServletResponse response) { + String authorizationCode = request.getParameter(PassportUtil.COMMON_CODE); + String state = request.getParameter(PassportUtil.COMMON_STATE); + if (!state.equals(request.getSession().getAttribute(SESSION_LINKEDIN_STATE))) { + String errMsg = UtilProperties.getMessage(resource, "LinkedInFailedToMatchState", UtilHttp.getLocale(request)); + request.setAttribute("_ERROR_MESSAGE_", errMsg); + return "error"; + } + if (UtilValidate.isEmpty(authorizationCode)) { + String error = request.getParameter(PassportUtil.COMMON_ERROR); + String errorDescpriton = request.getParameter(PassportUtil.COMMON_ERROR_DESCRIPTION); + String errMsg = null; + try { + errMsg = UtilProperties.getMessage(resource, "FailedToGetLinkedInAuthorizationCode", UtilMisc.toMap(PassportUtil.COMMON_ERROR, error, PassportUtil.COMMON_ERROR_DESCRIPTION, URLDecoder.decode(errorDescpriton, "UTF-8")), UtilHttp.getLocale(request)); + } catch (UnsupportedEncodingException e) { + errMsg = UtilProperties.getMessage(resource, "GetLinkedInAuthorizationCodeError", UtilHttp.getLocale(request)); + } + request.setAttribute("_ERROR_MESSAGE_", errMsg); + return "error"; + } + // Debug.logInfo("LinkedIn authorization code: " + authorizationCode, module); + + GenericValue oauth2LinkedIn = getOAuth2LinkedInConfig(request); + if (UtilValidate.isEmpty(oauth2LinkedIn)) { + String errMsg = UtilProperties.getMessage(resource, "GetOAuth2LinkedInConfigError", UtilHttp.getLocale(request)); + request.setAttribute("_ERROR_MESSAGE_", errMsg); + return "error"; + } + String clientId = oauth2LinkedIn.getString(PassportUtil.ApiKeyLabel); + String secret = oauth2LinkedIn.getString(PassportUtil.SecretKeyLabel); + String returnURI = oauth2LinkedIn.getString(envPrefix + PassportUtil.ReturnUrlLabel); + + // Grant token from authorization code and oauth2 token + // Use the authorization code to obtain an access token + String accessToken = null; + + HttpClient jsonClient = new HttpClient(); + PostMethod postMethod = new PostMethod(TokenEndpoint + TokenServiceUri); + try { + HttpMethodParams params = new HttpMethodParams(); + String queryString = "client_id=" + clientId + + "&client_secret=" + secret + + "&grant_type=authorization_code" + + "&code=" + authorizationCode + + "&redirect_uri=" + URLEncoder.encode(returnURI, "UTF-8"); + // Debug.logInfo("LinkedIn get access token query string: " + queryString, module); + postMethod.setQueryString(queryString); + params.setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY); + postMethod.setParams(params); + jsonClient.executeMethod(postMethod); + // Debug.logInfo("LinkedIn get access token response code: " + postMethod.getStatusCode(), module); + // Debug.logInfo("LinkedIn get access token response content: " + postMethod.getResponseBodyAsString(1024), module); + if (postMethod.getStatusCode() == HttpStatus.SC_OK) { + // Debug.logInfo("Json Response from LinkedIn: " + postMethod.getResponseBodyAsString(1024), module); + JSON jsonObject = JSON.from(postMethod.getResponseBodyAsString(1024)); + JSONToMap jsonMap = new JSONToMap(); + Map userMap = jsonMap.convert(jsonObject); + accessToken = (String) userMap.get("access_token"); + // Debug.logInfo("Generated Access Token : " + accessToken, module); + } else { + String errMsg = UtilProperties.getMessage(resource, "GetOAuth2LinkedInAccessTokenError", UtilMisc.toMap("error", postMethod.getResponseBodyAsString()), UtilHttp.getLocale(request)); + request.setAttribute("_ERROR_MESSAGE_", errMsg); + return "error"; + } + } catch (UnsupportedEncodingException e) { + request.setAttribute("_ERROR_MESSAGE_", e.toString()); + return "error"; + } catch (HttpException e) { + request.setAttribute("_ERROR_MESSAGE_", e.toString()); + return "error"; + } catch (IOException e) { + request.setAttribute("_ERROR_MESSAGE_", e.toString()); + return "error"; + } catch (ConversionException e) { + request.setAttribute("_ERROR_MESSAGE_", e.toString()); + return "error"; + } finally { + postMethod.releaseConnection(); + } + + // Get User Profile + GetMethod getMethod = new GetMethod(TokenEndpoint + UserApiUri + "?oauth2_access_token=" + accessToken); + Document userInfo = null; + try { + userInfo = LinkedInAuthenticator.getUserInfo(getMethod, UtilHttp.getLocale(request)); + } catch (HttpException e) { + request.setAttribute("_ERROR_MESSAGE_", e.toString()); + return "error"; + } catch (IOException e) { + request.setAttribute("_ERROR_MESSAGE_", e.toString()); + return "error"; + } catch (AuthenticatorException e) { + request.setAttribute("_ERROR_MESSAGE_", e.toString()); + return "error"; + } catch (SAXException e) { + request.setAttribute("_ERROR_MESSAGE_", e.toString()); + return "error"; + } catch (ParserConfigurationException e) { + request.setAttribute("_ERROR_MESSAGE_", e.toString()); + return "error"; + } finally { + getMethod.releaseConnection(); + } + // Debug.logInfo("LinkedIn User Info:" + userInfo, module); + + // Store the user info and check login the user + return checkLoginLinkedInUser(request, userInfo, accessToken); + } + + private static String checkLoginLinkedInUser(HttpServletRequest request, Document userInfo, String accessToken) { + Delegator delegator = (Delegator) request.getAttribute("delegator"); + LocalDispatcher dispatcher = (LocalDispatcher) request.getAttribute("dispatcher"); + String productStoreId = ProductStoreWorker.getProductStoreId(request); + String linkedInUserId = LinkedInAuthenticator.getLinkedInUserId(userInfo); + GenericValue linkedInUser = null; + try { + linkedInUser = delegator.findOne("LinkedInUser", UtilMisc.toMap("linkedInUserId", linkedInUserId), false); + } catch (GenericEntityException e) { + request.setAttribute("_ERROR_MESSAGE_", e.getMessage()); + return "error"; + } + if (UtilValidate.isNotEmpty(linkedInUser)) { + boolean dataChanged = false; + if (!accessToken.equals(linkedInUser.getString("accessToken"))) { + linkedInUser.set("accessToken", accessToken); + dataChanged = true; + } + if (!envPrefix.equals(linkedInUser.getString("envPrefix"))) { + linkedInUser.set("envPrefix", envPrefix); + dataChanged = true; + } + if (!productStoreId.equals(linkedInUser.getString("productStoreId"))) { + linkedInUser.set("productStoreId", productStoreId); + dataChanged = true; + } + if (dataChanged) { + try { + linkedInUser.store(); + } catch (GenericEntityException e) { + Debug.logError(e.getMessage(), module); + } + } + } else { + linkedInUser = delegator.makeValue("LinkedInUser", UtilMisc.toMap("accessToken", accessToken, + "productStoreId", productStoreId, + "envPrefix", envPrefix, + "linkedInUserId", linkedInUserId)); + try { + linkedInUser.create(); + } catch (GenericEntityException e) { + Debug.logError(e.getMessage(), module); + } + } + try { + GenericValue userLogin = EntityUtil.getFirst(delegator.findByAnd("UserLogin", UtilMisc.toMap("externalAuthId", linkedInUserId), null, false)); + LinkedInAuthenticator authn = new LinkedInAuthenticator(); + authn.initialize(dispatcher); + if (UtilValidate.isEmpty(userLogin)) { + String userLoginId = authn.createUser(userInfo); + userLogin = delegator.findOne("UserLogin", UtilMisc.toMap("userLoginId", userLoginId), false); + } + String password = PassportUtil.randomString(); + boolean useEncryption = "true".equals(UtilProperties.getPropertyValue("security.properties", "password.encrypt")); + userLogin.set("currentPassword", useEncryption ? HashCrypt.digestHash(LoginServices.getHashType(), null, password) : password); + userLogin.store(); + request.setAttribute("USERNAME", userLogin.getString("userLoginId")); + request.setAttribute("PASSWORD", password); + } catch (GenericEntityException e) { + Debug.logError(e.getMessage(), module); + request.setAttribute("_ERROR_MESSAGE_", e.toString()); + return "error"; + } catch (AuthenticatorException e) { + Debug.logError(e.getMessage(), module); + request.setAttribute("_ERROR_MESSAGE_", e.toString()); + return "error"; + } + return "success"; + } + + public static GenericValue getOAuth2LinkedInConfig(HttpServletRequest request) { + Delegator delegator = (Delegator) request.getAttribute("delegator"); + String productStoreId = ProductStoreWorker.getProductStoreId(request); + try { + return getOAuth2LinkedInConfig(delegator, productStoreId); + } catch (GenericEntityException e) { + Map messageMap = UtilMisc.toMap("errorMessage", e.toString()); + String errMsg = UtilProperties.getMessage(resource, "GetOAuth2LinkedInError", messageMap, UtilHttp.getLocale(request)); + request.setAttribute("_ERROR_MESSAGE_", errMsg); + } + return null; + } + + public static GenericValue getOAuth2LinkedInConfig(Delegator delegator, String productStoreId) throws GenericEntityException { + return EntityUtil.getFirst(EntityUtil.filterByDate(delegator.findByAnd("OAuth2LinkedIn", UtilMisc.toMap("productStoreId", productStoreId), null, false))); + } +} Index: specialpurpose/passport/src/org/ofbiz/passport/user/GitHubAuthenticator.java =================================================================== --- specialpurpose/passport/src/org/ofbiz/passport/user/GitHubAuthenticator.java (revision 0) +++ specialpurpose/passport/src/org/ofbiz/passport/user/GitHubAuthenticator.java (revision 0) @@ -0,0 +1,416 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + *******************************************************************************/ +package org.ofbiz.passport.user; + +import java.util.Locale; +import java.util.Map; +import java.io.IOException; +import java.io.Serializable; +import java.sql.Timestamp; + +import javax.transaction.Transaction; + +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpException; +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.cookie.CookiePolicy; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.params.HttpMethodParams; +import org.ofbiz.passport.event.GitHubEvents; +import org.ofbiz.passport.user.GitHubUserGroupMapper; +import org.ofbiz.passport.util.PassportUtil; +import org.ofbiz.common.authentication.api.Authenticator; +import org.ofbiz.common.authentication.api.AuthenticatorException; +import org.ofbiz.service.LocalDispatcher; +import org.ofbiz.service.GenericServiceException; +import org.ofbiz.service.ServiceUtil; +import org.ofbiz.entity.Delegator; +import org.ofbiz.entity.GenericValue; +import org.ofbiz.entity.GenericEntityException; +import org.ofbiz.entity.transaction.TransactionUtil; +import org.ofbiz.entity.transaction.GenericTransactionException; +import org.ofbiz.entity.util.EntityUtil; +import org.ofbiz.base.conversion.ConversionException; +import org.ofbiz.base.conversion.JSONConverters.JSONToMap; +import org.ofbiz.base.lang.JSON; +import org.ofbiz.base.util.UtilProperties; +import org.ofbiz.base.util.Debug; +import org.ofbiz.base.util.UtilMisc; +import org.ofbiz.base.util.UtilDateTime; +import org.ofbiz.base.util.UtilValidate; + +import javolution.util.FastMap; + +/** + * GitHub OFBiz Authenticator + */ +public class GitHubAuthenticator implements Authenticator { + + private static final String module = GitHubAuthenticator.class.getName(); + + public static final String props = "gitHubAuth.properties"; + + public static final String resource = "PassportUiLabels"; + + protected LocalDispatcher dispatcher; + + protected Delegator delegator; + + /** + * Method called when authenticator is first initialized (the delegator + * object can be obtained from the LocalDispatcher) + * + * @param dispatcher The ServiceDispatcher to use for this Authenticator + */ + public void initialize(LocalDispatcher dispatcher) { + this.dispatcher = dispatcher; + this.delegator = dispatcher.getDelegator(); + } + + /** + * Method to authenticate a user. + * + * For GitHub users, we only check if the username(userLoginId) exists an + * externalAuthId, and the externalAuthId has a valid accessToken in + * GitHubUser entity. + * + * @param username User's username + * @param password User's password + * @param isServiceAuth true if authentication is for a service call + * @return true if the user is authenticated + * @throws org.ofbiz.common.authentication.api.AuthenticatorException + * when a fatal error occurs during authentication + */ + public boolean authenticate(String userLoginId, String password, boolean isServiceAuth) throws AuthenticatorException { + Map user = null; + GetMethod getMethod = null; + try { + GenericValue userLogin = delegator.findOne("UserLogin", UtilMisc.toMap("userLoginId", userLoginId), false); + String externalAuthId = userLogin.getString("externalAuthId"); + GenericValue gitHubUser = delegator.findOne("GitHubUser", UtilMisc.toMap("gitHubUserId", externalAuthId), false); + if (UtilValidate.isNotEmpty(gitHubUser)) { + String accessToken = gitHubUser.getString("accessToken"); + String tokenType = gitHubUser.getString("tokenType"); + if (UtilValidate.isNotEmpty(accessToken)) { + getMethod = new GetMethod(GitHubEvents.ApiEndpoint + GitHubEvents.UserApiUri); + user = GitHubAuthenticator.getUserInfo(getMethod, accessToken, tokenType, Locale.getDefault()); + } + } + } catch (GenericEntityException e) { + throw new AuthenticatorException(e.getMessage(), e); + } catch (HttpException e) { + throw new AuthenticatorException(e.getMessage(), e); + } catch (IOException e) { + throw new AuthenticatorException(e.getMessage(), e); + } catch (AuthenticatorException e) { + throw new AuthenticatorException(e.getMessage(), e); + } finally { + if (getMethod != null) { + getMethod.releaseConnection(); + } + } + + Debug.logInfo("GitHub auth called; returned user info: " + user, module); + return user != null; + } + + /** + * Logs a user out + * + * @param username User's username + * @throws org.ofbiz.common.authentication.api.AuthenticatorException + * when logout fails + */ + public void logout(String username) throws AuthenticatorException { + } + + /** + * Reads user information and syncs it to OFBiz (i.e. UserLogin, Person, etc) + * + * @param userLoginId + * @throws org.ofbiz.common.authentication.api.AuthenticatorException + * user synchronization fails + */ + public void syncUser(String userLoginId) throws AuthenticatorException { + Map userMap = getGitHubUserinfo(userLoginId); + GenericValue system; + try { + system = delegator.findOne("UserLogin", UtilMisc.toMap("userLoginId", "system"), true); + } catch (GenericEntityException e) { + throw new AuthenticatorException(e.getMessage(), e); + } + + GenericValue userLogin; + try { + userLogin = EntityUtil.getFirst(delegator.findByAnd("UserLogin", UtilMisc.toMap("externalAuthId", (String) userMap.get("id")), null, false)); + } catch (GenericEntityException e) { + throw new AuthenticatorException(e.getMessage(), e); + } + + // suspend the current transaction and load the user + Transaction parentTx = null; + boolean beganTransaction = false; + + try { + try { + parentTx = TransactionUtil.suspend(); + } catch (GenericTransactionException e) { + Debug.logError(e, "Could not suspend transaction: " + e.getMessage(), module); + } + + try { + beganTransaction = TransactionUtil.begin(); + + if (userLogin == null) { + // create the user + createUser(userMap, system); + } else { + // update the user information + updateUser(userMap, system, userLogin); + } + + } catch (GenericTransactionException e) { + Debug.logError(e, "Could not suspend transaction: " + e.getMessage(), module); + } finally { + try { + TransactionUtil.commit(beganTransaction); + } catch (GenericTransactionException e) { + Debug.logError(e, "Could not commit nested transaction: " + e.getMessage(), module); + } + } + } finally { + // resume/restore parent transaction + if (parentTx != null) { + try { + TransactionUtil.resume(parentTx); + Debug.logVerbose("Resumed the parent transaction.", module); + } catch (GenericTransactionException e) { + Debug.logError(e, "Could not resume parent nested transaction: " + e.getMessage(), module); + } + } + } + } + + private Map getGitHubUserinfo(String userLoginId) throws AuthenticatorException { + Map user = null; + GetMethod getMethod = null; + try { + GenericValue userLogin = delegator.findOne("UserLogin", UtilMisc.toMap("userLoginId", userLoginId), false); + String externalAuthId = userLogin.getString("externalAuthId"); + GenericValue gitHubUser = delegator.findOne("GitHubUser", UtilMisc.toMap("gitHubUserId", externalAuthId), false); + if (UtilValidate.isNotEmpty(gitHubUser)) { + String accessToken = gitHubUser.getString("accessToken"); + String tokenType = gitHubUser.getString("tokenType"); + if (UtilValidate.isNotEmpty(accessToken)) { + getMethod = new GetMethod(GitHubEvents.ApiEndpoint + GitHubEvents.UserApiUri); + user = getUserInfo(getMethod, accessToken, tokenType, Locale.getDefault()); + } + } + } catch (GenericEntityException e) { + throw new AuthenticatorException(e.getMessage(), e); + } catch (HttpException e) { + throw new AuthenticatorException(e.getMessage(), e); + } catch (IOException e) { + throw new AuthenticatorException(e.getMessage(), e); + } catch (AuthenticatorException e) { + throw new AuthenticatorException(e.getMessage(), e); + } finally { + if (getMethod != null) { + getMethod.releaseConnection(); + } + } + return user; + } + + public String createUser(Map userMap) throws AuthenticatorException { + GenericValue system; + try { + system = delegator.findOne("UserLogin", UtilMisc.toMap("userLoginId", "system"), true); + } catch (GenericEntityException e) { + throw new AuthenticatorException(e.getMessage(), e); + } + return createUser(userMap, system); + } + + private String createUser(Map userMap, GenericValue system) throws AuthenticatorException { + // create person + userLogin + Map createPersonUlMap = FastMap.newInstance(); + String userLoginId = delegator.getNextSeqId("UserLogin"); + if (userMap.containsKey("name")) { + // use github's name as OFBiz's lastName + createPersonUlMap.put("lastName", (String) userMap.get("name")); + } + if (userMap.containsKey("login")) { + createPersonUlMap.put("externalAuthId", (String) userMap.get("login")); + } + // createPersonUlMap.put("externalId", user.getUserId()); + createPersonUlMap.put("userLoginId", userLoginId); + createPersonUlMap.put("currentPassword", "[EXTERNAL]"); + createPersonUlMap.put("currentPasswordVerify", "[EXTERNAL]"); + createPersonUlMap.put("userLogin", system); + Map createPersonResult; + try { + createPersonResult = dispatcher.runSync("createPersonAndUserLogin", createPersonUlMap); + } catch (GenericServiceException e) { + throw new AuthenticatorException(e.getMessage(), e); + } + if (ServiceUtil.isError(createPersonResult)) { + throw new AuthenticatorException(ServiceUtil.getErrorMessage(createPersonResult)); + } + String partyId = (String) createPersonResult.get("partyId"); + + // give this person a role of CUSTOMER + GenericValue partyRole = delegator.makeValue("PartyRole", UtilMisc.toMap("partyId", partyId, "roleTypeId", "CUSTOMER")); + try { + delegator.create(partyRole); + } catch (GenericEntityException e) { + Debug.logError(e, module); + throw new AuthenticatorException(e.getMessage(), e); + } + + // create email + if (userMap.containsKey("email")) { + Map createEmailMap = FastMap.newInstance(); + createEmailMap.put("emailAddress", (String) userMap.get("email")); + createEmailMap.put("contactMechPurposeTypeId", "PRIMARY_EMAIL"); + createEmailMap.put("partyId", partyId); + createEmailMap.put("userLogin", system); + Map createEmailResult; + try { + createEmailResult = dispatcher.runSync("createPartyEmailAddress", createEmailMap); + } catch (GenericServiceException e) { + throw new AuthenticatorException(e.getMessage(), e); + } + if (ServiceUtil.isError(createEmailResult)) { + throw new AuthenticatorException(ServiceUtil.getErrorMessage(createEmailResult)); + } + } + + // create security group(s) + Timestamp now = UtilDateTime.nowTimestamp(); + for (String securityGroup : (new GitHubUserGroupMapper(new String[] {(String) userMap.get("type")}).getSecurityGroups())) { + // check and make sure the security group exists + GenericValue secGroup = null; + try { + secGroup = delegator.findOne("SecurityGroup", UtilMisc.toMap("groupId", securityGroup), true); + } catch (GenericEntityException e) { + Debug.logError(e, e.getMessage(), module); + } + + // add it to the user if it exists + if (secGroup != null) { + Map createSecGrpMap = FastMap.newInstance(); + createSecGrpMap.put("userLoginId", userLoginId); + createSecGrpMap.put("groupId", securityGroup); + createSecGrpMap.put("fromDate", now); + createSecGrpMap.put("userLogin", system); + + Map createSecGrpResult; + try { + createSecGrpResult = dispatcher.runSync("addUserLoginToSecurityGroup", createSecGrpMap); + } catch (GenericServiceException e) { + throw new AuthenticatorException(e.getMessage(), e); + } + if (ServiceUtil.isError(createSecGrpResult)) { + throw new AuthenticatorException(ServiceUtil.getErrorMessage(createSecGrpResult)); + } + } + } + return userLoginId; + } + + private void updateUser(Map userMap, GenericValue system, GenericValue userLogin) throws AuthenticatorException { + // TODO implement me + } + + /** + * Updates a user's password. + * + * @param username User's username + * @param password User's current password + * @param newPassword User's new password + * @throws org.ofbiz.common.authentication.api.AuthenticatorException + * when update password fails + */ + public void updatePassword(String username, String password, String newPassword) throws AuthenticatorException { + Debug.logInfo("Calling GitHub:updatePassword() - ignored!!!", module); + } + + /** + * Weight of this authenticator (lower weights are run first) + * + * @return the weight of this Authenicator + */ + public float getWeight() { + return 1; + } + + /** + * Is the user synchronzied back to OFBiz + * + * @return true if the user record is copied to the OFB database + */ + public boolean isUserSynchronized() { + return true; + } + + /** + * Is this expected to be the only authenticator, if so errors will be thrown when users cannot be found + * + * @return true if this is expected to be the only Authenticator + */ + public boolean isSingleAuthenticator() { + return false; + } + + /** + * Flag to test if this Authenticator is enabled + * + * @return true if the Authenticator is enabled + */ + public boolean isEnabled() { + return "true".equalsIgnoreCase(UtilProperties.getPropertyValue(props, "github.authenticator.enabled", "true")); + } + + public static Map getUserInfo(GetMethod getMethod, String accessToken, String tokenType, Locale locale) throws HttpException, IOException, AuthenticatorException { + JSON userInfo = null; + HttpClient jsonClient = new HttpClient(); + HttpMethodParams params = new HttpMethodParams(); + params.setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY); + getMethod.setParams(params); + getMethod.setRequestHeader(PassportUtil.AUTHORIZATION_HEADER, tokenType + " " + accessToken); + getMethod.setRequestHeader(PassportUtil.ACCEPT_HEADER, "application/json"); + jsonClient.executeMethod(getMethod); + if (getMethod.getStatusCode() == HttpStatus.SC_OK) { + Debug.logInfo("Json Response from GitHub: " + getMethod.getResponseBodyAsString(), module); + userInfo = JSON.from(getMethod.getResponseBodyAsString()); + } else { + String errMsg = UtilProperties.getMessage(resource, "GetOAuth2AccessTokenError", UtilMisc.toMap("error", getMethod.getResponseBodyAsString()), locale); + throw new AuthenticatorException(errMsg); + } + JSONToMap jsonMap = new JSONToMap(); + Map userMap; + try { + userMap = jsonMap.convert(userInfo); + } catch (ConversionException e) { + throw new AuthenticatorException(e.getMessage()); + } + return userMap; + } +} Index: specialpurpose/passport/src/org/ofbiz/passport/user/GitHubUserGroupMapper.java =================================================================== --- specialpurpose/passport/src/org/ofbiz/passport/user/GitHubUserGroupMapper.java (revision 0) +++ specialpurpose/passport/src/org/ofbiz/passport/user/GitHubUserGroupMapper.java (revision 0) @@ -0,0 +1,72 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + *******************************************************************************/ +package org.ofbiz.passport.user; + +import java.util.List; +import java.util.Properties; +import java.util.Arrays; +import java.util.Set; + +import org.ofbiz.base.util.UtilProperties; + +import javolution.util.FastList; +import javolution.util.FastSet; + +/** + * GitHub UserGroupMapper + */ +public class GitHubUserGroupMapper { + + protected List groups; + + public GitHubUserGroupMapper(String[] groups) { + this.groups = Arrays.asList(groups); + } + + public GitHubUserGroupMapper(String group) { + if (groups == null) { + groups = FastList.newInstance(); + } + groups.add(group); + } + + public Set getSecurityGroups() { + Properties props = UtilProperties.getProperties(GitHubAuthenticator.props); + + Set secGroups = FastSet.newInstance(); + boolean running = true; + int index = 1; + + while (running) { + String groupStr = (String) props.get("github.group.map." + index); + if (groupStr == null) { + running = false; + } else { + String[] groupSplit = groupStr.split("="); + if (groupSplit.length == 2) { + if (groups.contains(groupSplit[0])) { + secGroups.add(groupSplit[1]); + } + } + } + index++; + } + return secGroups; + } +} Index: specialpurpose/passport/src/org/ofbiz/passport/user/LinkedInAuthenticator.java =================================================================== --- specialpurpose/passport/src/org/ofbiz/passport/user/LinkedInAuthenticator.java (revision 0) +++ specialpurpose/passport/src/org/ofbiz/passport/user/LinkedInAuthenticator.java (revision 0) @@ -0,0 +1,471 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + *******************************************************************************/ +package org.ofbiz.passport.user; + +import java.util.Locale; +import java.util.Map; +import java.io.IOException; +import java.io.Serializable; +import java.sql.Timestamp; + +import javax.transaction.Transaction; +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpException; +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.cookie.CookiePolicy; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.params.HttpMethodParams; +import org.ofbiz.passport.event.LinkedInEvents; +import org.ofbiz.common.authentication.api.Authenticator; +import org.ofbiz.common.authentication.api.AuthenticatorException; +import org.ofbiz.service.LocalDispatcher; +import org.ofbiz.service.GenericServiceException; +import org.ofbiz.service.ServiceUtil; +import org.ofbiz.entity.Delegator; +import org.ofbiz.entity.GenericValue; +import org.ofbiz.entity.GenericEntityException; +import org.ofbiz.entity.transaction.TransactionUtil; +import org.ofbiz.entity.transaction.GenericTransactionException; +import org.ofbiz.entity.util.EntityUtil; +import org.ofbiz.base.util.UtilProperties; +import org.ofbiz.base.util.Debug; +import org.ofbiz.base.util.UtilMisc; +import org.ofbiz.base.util.UtilDateTime; +import org.ofbiz.base.util.UtilValidate; +import org.ofbiz.base.util.UtilXml; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import javolution.util.FastMap; + +/** + * LinkedIn OFBiz Authenticator + */ +public class LinkedInAuthenticator implements Authenticator { + + private static final String module = LinkedInAuthenticator.class.getName(); + + public static final String props = "linkedInAuth.properties"; + + public static final String resource = "PassportUiLabels"; + + protected LocalDispatcher dispatcher; + + protected Delegator delegator; + + /** + * Method called when authenticator is first initialized (the delegator + * object can be obtained from the LocalDispatcher) + * + * @param dispatcher The ServiceDispatcher to use for this Authenticator + */ + public void initialize(LocalDispatcher dispatcher) { + this.dispatcher = dispatcher; + this.delegator = dispatcher.getDelegator(); + } + + /** + * Method to authenticate a user. + * + * For LinkedIn users, we only check if the username(userLoginId) exists an + * externalAuthId, and the externalAuthId has a valid accessToken in + * LinkedInUser entity. + * + * @param username User's username + * @param password User's password + * @param isServiceAuth true if authentication is for a service call + * @return true if the user is authenticated + * @throws org.ofbiz.common.authentication.api.AuthenticatorException + * when a fatal error occurs during authentication + */ + public boolean authenticate(String userLoginId, String password, boolean isServiceAuth) throws AuthenticatorException { + Document user = null; + GetMethod getMethod = null; + try { + GenericValue userLogin = delegator.findOne("UserLogin", UtilMisc.toMap("userLoginId", userLoginId), false); + String externalAuthId = userLogin.getString("externalAuthId"); + GenericValue linkedInUser = delegator.findOne("LinkedInUser", UtilMisc.toMap("linedInUserId", externalAuthId), false); + if (UtilValidate.isNotEmpty(linkedInUser)) { + String accessToken = linkedInUser.getString("accessToken"); + if (UtilValidate.isNotEmpty(accessToken)) { + getMethod = new GetMethod(LinkedInEvents.TokenEndpoint + LinkedInEvents.UserApiUri + "?oauth2_access_token=" + accessToken); + user = LinkedInAuthenticator.getUserInfo(getMethod, Locale.getDefault()); + } + } + } catch (GenericEntityException e) { + throw new AuthenticatorException(e.getMessage(), e); + } catch (HttpException e) { + throw new AuthenticatorException(e.getMessage(), e); + } catch (IOException e) { + throw new AuthenticatorException(e.getMessage(), e); + } catch (AuthenticatorException e) { + throw new AuthenticatorException(e.getMessage(), e); + } catch (SAXException e) { + throw new AuthenticatorException(e.getMessage(), e); + } catch (ParserConfigurationException e) { + throw new AuthenticatorException(e.getMessage(), e); + } finally { + if (getMethod != null) { + getMethod.releaseConnection(); + } + } + + Debug.logInfo("LinkedIn auth called; returned user info: " + user, module); + return user != null; + } + + /** + * Logs a user out + * + * @param username User's username + * @throws org.ofbiz.common.authentication.api.AuthenticatorException + * when logout fails + */ + public void logout(String username) throws AuthenticatorException { + } + + /** + * Reads user information and syncs it to OFBiz (i.e. UserLogin, Person, etc) + * + * @param userLoginId + * @throws org.ofbiz.common.authentication.api.AuthenticatorException + * user synchronization fails + */ + public void syncUser(String userLoginId) throws AuthenticatorException { + Document user = getLinkedInUserinfo(userLoginId); + + GenericValue system; + try { + system = delegator.findOne("UserLogin", UtilMisc.toMap("userLoginId", "system"), true); + } catch (GenericEntityException e) { + throw new AuthenticatorException(e.getMessage(), e); + } + + GenericValue userLogin; + try { + userLogin = EntityUtil.getFirst(delegator.findByAnd("UserLogin", UtilMisc.toMap("externalAuthId", getLinkedInUserId(user)), null, false)); + } catch (GenericEntityException e) { + throw new AuthenticatorException(e.getMessage(), e); + } + + // suspend the current transaction and load the user + Transaction parentTx = null; + boolean beganTransaction = false; + + try { + try { + parentTx = TransactionUtil.suspend(); + } catch (GenericTransactionException e) { + Debug.logError(e, "Could not suspend transaction: " + e.getMessage(), module); + } + + try { + beganTransaction = TransactionUtil.begin(); + + if (userLogin == null) { + // create the user + createUser(user, system); + } else { + // update the user information + updateUser(user, system, userLogin); + } + + } catch (GenericTransactionException e) { + Debug.logError(e, "Could not suspend transaction: " + e.getMessage(), module); + } finally { + try { + TransactionUtil.commit(beganTransaction); + } catch (GenericTransactionException e) { + Debug.logError(e, "Could not commit nested transaction: " + e.getMessage(), module); + } + } + } finally { + // resume/restore parent transaction + if (parentTx != null) { + try { + TransactionUtil.resume(parentTx); + Debug.logVerbose("Resumed the parent transaction.", module); + } catch (GenericTransactionException e) { + Debug.logError(e, "Could not resume parent nested transaction: " + e.getMessage(), module); + } + } + } + } + + private Document getLinkedInUserinfo(String userLoginId) throws AuthenticatorException { + Document user = null; + GetMethod getMethod = null; + try { + GenericValue userLogin = delegator.findOne("UserLogin", UtilMisc.toMap("userLoginId", userLoginId), false); + String externalAuthId = userLogin.getString("externalAuthId"); + GenericValue linkedInUser = delegator.findOne("LinkedInUser", UtilMisc.toMap("linkedInUserId", externalAuthId), false); + if (UtilValidate.isNotEmpty(linkedInUser)) { + String accessToken = linkedInUser.getString("accessToken"); + if (UtilValidate.isNotEmpty(accessToken)) { + getMethod = new GetMethod(LinkedInEvents.TokenEndpoint + LinkedInEvents.UserApiUri + "?oauth2_access_token=" + accessToken); + user = getUserInfo(getMethod, Locale.getDefault()); + } + } + } catch (GenericEntityException e) { + throw new AuthenticatorException(e.getMessage(), e); + } catch (HttpException e) { + throw new AuthenticatorException(e.getMessage(), e); + } catch (IOException e) { + throw new AuthenticatorException(e.getMessage(), e); + } catch (AuthenticatorException e) { + throw new AuthenticatorException(e.getMessage(), e); + } catch (SAXException e) { + throw new AuthenticatorException(e.getMessage(), e); + } catch (ParserConfigurationException e) { + throw new AuthenticatorException(e.getMessage(), e); + } finally { + if (getMethod != null) { + getMethod.releaseConnection(); + } + } + return user; + } + + public String createUser(Document user) throws AuthenticatorException { + GenericValue system; + try { + system = delegator.findOne("UserLogin", UtilMisc.toMap("userLoginId", "system"), true); + } catch (GenericEntityException e) { + throw new AuthenticatorException(e.getMessage(), e); + } + return createUser(user, system); + } + + private String createUser(Document user, GenericValue system) throws AuthenticatorException { + Map userInfo = parseLinkedInUserInfo(user); + + // create person + userLogin + Map createPersonUlMap = FastMap.newInstance(); + String userLoginId = delegator.getNextSeqId("UserLogin"); + if (userInfo.containsKey("firstName")) { + createPersonUlMap.put("firstName", userInfo.get("firstName")); + } + if (userInfo.containsKey("lastName")) { + createPersonUlMap.put("lastName", userInfo.get("lastName")); + } + if (userInfo.containsKey("userId")) { + createPersonUlMap.put("externalAuthId", userInfo.get("userId")); + } + // createPersonUlMap.put("externalId", user.getUserId()); + createPersonUlMap.put("userLoginId", userLoginId); + createPersonUlMap.put("currentPassword", "[EXTERNAL]"); + createPersonUlMap.put("currentPasswordVerify", "[EXTERNAL]"); + createPersonUlMap.put("userLogin", system); + Map createPersonResult; + try { + createPersonResult = dispatcher.runSync("createPersonAndUserLogin", createPersonUlMap); + } catch (GenericServiceException e) { + throw new AuthenticatorException(e.getMessage(), e); + } + if (ServiceUtil.isError(createPersonResult)) { + throw new AuthenticatorException(ServiceUtil.getErrorMessage(createPersonResult)); + } + String partyId = (String) createPersonResult.get("partyId"); + + // give this person a role of CUSTOMER + GenericValue partyRole = delegator.makeValue("PartyRole", UtilMisc.toMap("partyId", partyId, "roleTypeId", "CUSTOMER")); + try { + delegator.create(partyRole); + } catch (GenericEntityException e) { + Debug.logError(e, module); + throw new AuthenticatorException(e.getMessage(), e); + } + + // create email + if (userInfo.containsKey("emailAddress")) { + Map createEmailMap = FastMap.newInstance(); + createEmailMap.put("emailAddress", userInfo.get("emailAddress")); + createEmailMap.put("contactMechPurposeTypeId", "PRIMARY_EMAIL"); + createEmailMap.put("partyId", partyId); + createEmailMap.put("userLogin", system); + Map createEmailResult; + try { + createEmailResult = dispatcher.runSync("createPartyEmailAddress", createEmailMap); + } catch (GenericServiceException e) { + throw new AuthenticatorException(e.getMessage(), e); + } + if (ServiceUtil.isError(createEmailResult)) { + throw new AuthenticatorException(ServiceUtil.getErrorMessage(createEmailResult)); + } + } + + // create security group(s) + Timestamp now = UtilDateTime.nowTimestamp(); + for (String securityGroup : (new LinkedInUserGroupMapper(new String[] {"person"}).getSecurityGroups())) { + // check and make sure the security group exists + GenericValue secGroup = null; + try { + secGroup = delegator.findOne("SecurityGroup", UtilMisc.toMap("groupId", securityGroup), true); + } catch (GenericEntityException e) { + Debug.logError(e, e.getMessage(), module); + } + + // add it to the user if it exists + if (secGroup != null) { + Map createSecGrpMap = FastMap.newInstance(); + createSecGrpMap.put("userLoginId", userLoginId); + createSecGrpMap.put("groupId", securityGroup); + createSecGrpMap.put("fromDate", now); + createSecGrpMap.put("userLogin", system); + + Map createSecGrpResult; + try { + createSecGrpResult = dispatcher.runSync("addUserLoginToSecurityGroup", createSecGrpMap); + } catch (GenericServiceException e) { + throw new AuthenticatorException(e.getMessage(), e); + } + if (ServiceUtil.isError(createSecGrpResult)) { + throw new AuthenticatorException(ServiceUtil.getErrorMessage(createSecGrpResult)); + } + } + } + return userLoginId; + } + + private void updateUser(Document user, GenericValue system, GenericValue userLogin) throws AuthenticatorException { + // TODO implement me + } + + /** + * Updates a user's password. + * + * @param username User's username + * @param password User's current password + * @param newPassword User's new password + * @throws org.ofbiz.common.authentication.api.AuthenticatorException + * when update password fails + */ + public void updatePassword(String username, String password, String newPassword) throws AuthenticatorException { + Debug.logInfo("Calling LinkedIn:updatePassword() - ignored!!!", module); + } + + /** + * Weight of this authenticator (lower weights are run first) + * + * @return the weight of this Authenicator + */ + public float getWeight() { + return 1; + } + + /** + * Is the user synchronzied back to OFBiz + * + * @return true if the user record is copied to the OFB database + */ + public boolean isUserSynchronized() { + return true; + } + + /** + * Is this expected to be the only authenticator, if so errors will be thrown when users cannot be found + * + * @return true if this is expected to be the only Authenticator + */ + public boolean isSingleAuthenticator() { + return false; + } + + /** + * Flag to test if this Authenticator is enabled + * + * @return true if the Authenticator is enabled + */ + public boolean isEnabled() { + return "true".equalsIgnoreCase(UtilProperties.getPropertyValue(props, "linked.authenticator.enabled", "true")); + } + + public static Document getUserInfo(GetMethod getMethod, Locale locale) throws HttpException, IOException, AuthenticatorException, SAXException, ParserConfigurationException { + Document userInfo = null; + HttpClient jsonClient = new HttpClient(); + HttpMethodParams params = new HttpMethodParams(); + params.setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY); + getMethod.setParams(params); + jsonClient.executeMethod(getMethod); + if (getMethod.getStatusCode() == HttpStatus.SC_OK) { + Debug.logInfo("Json Response from LinkedIn: " + getMethod.getResponseBodyAsString(), module); + userInfo = UtilXml.readXmlDocument(getMethod.getResponseBodyAsString()); + } else { + String errMsg = UtilProperties.getMessage(resource, "GetOAuth2AccessTokenError", UtilMisc.toMap("error", getMethod.getResponseBodyAsString()), locale); + throw new AuthenticatorException(errMsg); + } + return userInfo; + } + + public static String getLinkedInUserId(Document userInfo) { + NodeList persons = userInfo.getElementsByTagName("person"); + if (UtilValidate.isEmpty(persons) || persons.getLength() <= 0) { + return null; + } + Element standardProfileRequest = UtilXml.firstChildElement((Element) persons.item(0), "site-standard-profile-request"); + Element url = UtilXml.firstChildElement(standardProfileRequest, "url"); + if (UtilValidate.isNotEmpty(url)) { + String urlContent = url.getTextContent(); + if (UtilValidate.isNotEmpty(urlContent)) { + String id = urlContent.substring(urlContent.indexOf("?id=")); + id = id.substring(0, id.indexOf("&")); + Debug.logInfo("LinkedIn user id: " + id, module); + return id; + } + } + return null; + } + + public static Map parseLinkedInUserInfo(Document userInfo) { + Map results = FastMap.newInstance(); + NodeList persons = userInfo.getElementsByTagName("person"); + if (UtilValidate.isEmpty(persons) || persons.getLength() <= 0) { + return results; + } + Element person = (Element) persons.item(0); + Element standardProfileRequest = UtilXml.firstChildElement(person, "site-standard-profile-request"); + Element url = UtilXml.firstChildElement(standardProfileRequest, "url"); + if (UtilValidate.isNotEmpty(url)) { + String urlContent = url.getTextContent(); + if (UtilValidate.isNotEmpty(urlContent)) { + String id = urlContent.substring(urlContent.indexOf("?id=")); + id = id.substring(0, id.indexOf("&")); + Debug.logInfo("LinkedIn user id: " + id, module); + results.put("userId", id); + } + } + Element firstNameElement = UtilXml.firstChildElement(person, "first-name"); + if (UtilValidate.isNotEmpty(firstNameElement) && UtilValidate.isNotEmpty(firstNameElement.getTextContent())) { + results.put("firstName", firstNameElement.getTextContent()); + } + Element lastNameElement = UtilXml.firstChildElement(person, "last-name"); + if (UtilValidate.isNotEmpty(lastNameElement) && UtilValidate.isNotEmpty(lastNameElement.getTextContent())) { + results.put("lastName", lastNameElement.getTextContent()); + } + Element emailElement = UtilXml.firstChildElement(person, "email-address"); + if (UtilValidate.isNotEmpty(emailElement) && UtilValidate.isNotEmpty(emailElement.getTextContent())) { + results.put("emailAddress", emailElement.getTextContent()); + } + return results; + } +} Index: specialpurpose/passport/src/org/ofbiz/passport/user/LinkedInUserGroupMapper.java =================================================================== --- specialpurpose/passport/src/org/ofbiz/passport/user/LinkedInUserGroupMapper.java (revision 0) +++ specialpurpose/passport/src/org/ofbiz/passport/user/LinkedInUserGroupMapper.java (revision 0) @@ -0,0 +1,72 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + *******************************************************************************/ +package org.ofbiz.passport.user; + +import java.util.List; +import java.util.Properties; +import java.util.Arrays; +import java.util.Set; + +import org.ofbiz.base.util.UtilProperties; + +import javolution.util.FastList; +import javolution.util.FastSet; + +/** + * LinkedIn UserGroupMapper + */ +public class LinkedInUserGroupMapper { + + protected List groups; + + public LinkedInUserGroupMapper(String[] groups) { + this.groups = Arrays.asList(groups); + } + + public LinkedInUserGroupMapper(String group) { + if (groups == null) { + groups = FastList.newInstance(); + } + groups.add(group); + } + + public Set getSecurityGroups() { + Properties props = UtilProperties.getProperties(LinkedInAuthenticator.props); + + Set secGroups = FastSet.newInstance(); + boolean running = true; + int index = 1; + + while (running) { + String groupStr = (String) props.get("linkedin.group.map." + index); + if (groupStr == null) { + running = false; + } else { + String[] groupSplit = groupStr.split("="); + if (groupSplit.length == 2) { + if (groups.contains(groupSplit[0])) { + secGroups.add(groupSplit[1]); + } + } + } + index++; + } + return secGroups; + } +} Index: specialpurpose/passport/src/org/ofbiz/passport/util/PassportUtil.java =================================================================== --- specialpurpose/passport/src/org/ofbiz/passport/util/PassportUtil.java (revision 0) +++ specialpurpose/passport/src/org/ofbiz/passport/util/PassportUtil.java (revision 0) @@ -0,0 +1,196 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + *******************************************************************************/ +package org.ofbiz.passport.util; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import javax.servlet.http.HttpServletRequest; + +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.SSLContextBuilder; +import org.apache.http.conn.ssl.TrustSelfSignedStrategy; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.ofbiz.base.util.Debug; + +public class PassportUtil { + + public static final String module = PassportUtil.class.getName(); + + public static final String ClientIdLabel = "ClientId"; + + public static final String SecretLabel = "Secret"; + + public static final String ReturnUrlLabel = "ReturnUrl"; + + public static final String TokenEndpointLabel = "TokenEndpoint"; + + public static final String GrantTypeLabel = "grantType"; + + public static final String ContentTypeLabel = "contentType"; + + public static final String AUTHORIZATION_HEADER = "Authorization"; + + public static final String UserProfileUrlLabel = "UserProfileUrl"; + + public static final String GrantTypeParam = "grant_type"; + + public static final String ContentTypeParam = "Content-Type"; + + public static final String ACCEPT_HEADER = "Accept"; + + public static final String APPLICATION_JSON = "application/json"; + + public static final String RESTApiEndpointLabel = "RESTApiEndpoint"; + + public static final String COMMON_CODE = "code"; + + public static final String COMMON_SCOPE = "scope"; + + public static final String AuthorizationCodeGrantType = "authorization_code"; + + public static final String COMMON_STATE = "state"; + + public static final String COMMON_ERROR = "error"; + + public static final String COMMON_ERROR_DESCRIPTION = "error_description"; + + public static final String ApiKeyLabel = "apiKey"; + + public static final String SecretKeyLabel = "secretKey"; + + public static final String COMMON_CLIENT_ID = "clientId"; + + public static final String COMMON_RETURN_RUL = "returnUrl"; + + public static final String COMMON_CLIENT_SECRET = "clientSecret"; + + public static final String ApiIdLabel = "apiId"; + + public static final String AppKeyLabel = "appKey"; + + public static final String AppSecretLabel = "appSecret"; + + public static final String AppIdLabel = "appId"; + + public static final String COMMON_APP_KEY = "AppKey"; + + public static final String COMMON_APP_SECRET = "AppSecret"; + + protected PassportUtil() { + // empty constructor + } + + public static PassportUtil getInstance() { + return new PassportUtil(); + } + + public static String getEnvPrefixByHost(HttpServletRequest request) { + String prefix ="test"; + try { + InetAddress[] addresses = InetAddress.getAllByName(request.getServerName()); + for (InetAddress address : addresses) { + if (address.isAnyLocalAddress() || address.isLinkLocalAddress() || address.isLoopbackAddress()) { + return prefix; + } + } + prefix = "live"; + } catch (UnknownHostException e) { + Debug.logError(e.getMessage(), module); + } + return prefix; + } + + private static String randomString(int lo, int hi) { + int n = rand(lo, hi); + byte b[] = new byte[n]; + for (int i = 0; i < n; i++) { + b[i] = (byte)rand('a', 'z'); + } + return new String(b); + } + + private static int rand(int lo, int hi) { + java.util.Random rn = new java.util.Random(); + int n = hi - lo + 1; + int i = rn.nextInt() % n; + if (i < 0) + i = -i; + return lo + i; + } + + public static String randomString() { + return randomString(8, 15); + } + + public CloseableHttpClient getAllowAllHttpClient() { + try { + SSLContextBuilder builder = new SSLContextBuilder(); + builder.loadTrustMaterial(null, new TrustSelfSignedStrategy()); + SSLConnectionSocketFactory sf = new AllowAllSSLSocketFactory(builder.build()); + CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sf).build(); + return httpclient; + } catch (Exception e) { + return HttpClients.createDefault(); + } + } + + public class AllowAllSSLSocketFactory extends SSLConnectionSocketFactory { + SSLContext sslContext = SSLContext.getInstance("TLS"); + + public AllowAllSSLSocketFactory(SSLContext sslContext) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { + super(sslContext); + + TrustManager tm = new X509TrustManager() { + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public X509Certificate[] getAcceptedIssuers() { + return null; + } + }; + + sslContext.init(null, new TrustManager[] { tm }, null); + } + + public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException { + return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose); + } + + public Socket createSocket() throws IOException { + return sslContext.getSocketFactory().createSocket(); + } + } + +} \ No newline at end of file Index: specialpurpose/passport/webapp/passport/WEB-INF/actions/login/getThirdPartyLogins.groovy =================================================================== --- specialpurpose/passport/webapp/passport/WEB-INF/actions/login/getThirdPartyLogins.groovy (revision 0) +++ specialpurpose/passport/webapp/passport/WEB-INF/actions/login/getThirdPartyLogins.groovy (revision 0) @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.ofbiz.passport; + +import org.ofbiz.entity.GenericValue; +import org.ofbiz.entity.util.EntityUtil; +import org.ofbiz.base.util.Debug; +import org.ofbiz.product.store.ProductStoreWorker; + +final String module = "getThirdPartyLogins.groovy" + +adminErrorInfo = context.adminErrorInfo; +productStoreId = context.productStoreId; +if (!productStoreId) { + productStore = ProductStoreWorker.getProductStore(request); + productStoreId = productStore.productStoreId; +} + +if (!adminErrorInfo || !adminErrorInfo.hasError()) { + storePassportLoginMethList = null; + // Get lists of passport login methods + if (productStoreId) { + storePassportLoginMethList = delegator.findByAnd("ThirdPartyLogin", ["productStoreId": productStoreId], ["sequenceNum ASC"], false); + storePassportLoginMethList = EntityUtil.filterByDate(storePassportLoginMethList); + } + + // Extra data preparation for each login method. + if (storePassportLoginMethList) { + storeLoginMethList = [] + for (storeLoginMeth in storePassportLoginMethList) { + storeLoginMethDetail = EntityUtil.getFirst(EntityUtil.filterByDate(delegator.findByAnd(storeLoginMeth.loginMethTypeId + storeLoginMeth.loginProviderId, ["productStoreId": productStoreId], null, false))); + storeLoginMethList.add(storeLoginMethDetail) + } + context.storeLoginMethList = storeLoginMethList + } +} Index: specialpurpose/passport/webapp/passport/WEB-INF/controller-passport.xml =================================================================== --- specialpurpose/passport/webapp/passport/WEB-INF/controller-passport.xml (revision 0) +++ specialpurpose/passport/webapp/passport/WEB-INF/controller-passport.xml (revision 0) @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: specialpurpose/passport/webapp/passport/login/thirdPartyLogins.ftl =================================================================== --- specialpurpose/passport/webapp/passport/login/thirdPartyLogins.ftl (revision 0) +++ specialpurpose/passport/webapp/passport/login/thirdPartyLogins.ftl (revision 0) @@ -0,0 +1,29 @@ +<#-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +--> + +<#if storeLoginMethList?exists && storeLoginMethList?has_content> +
+

${uiLabelMap.ThirdPartyLogin}

+
+ <#list storeLoginMethList as storeLoginMeth> + + +
+
+ Index: specialpurpose/passport/widget/PassportScreens.xml =================================================================== --- specialpurpose/passport/widget/PassportScreens.xml (revision 0) +++ specialpurpose/passport/widget/PassportScreens.xml (revision 0) @@ -0,0 +1,42 @@ + + + + + + + + +
+ + +